//! Vote state, vote program
//! Receive and processes votes from validators
pub use solana_program::vote::state::{vote_state_versions::*, *};
use {
    log::*,
    serde_derive::{Deserialize, Serialize},
    solana_metrics::datapoint_debug,
    solana_program::vote::{error::VoteError, program::id, state::serde_compact_vote_state_update},
    solana_sdk::{
        account::{AccountSharedData, ReadableAccount, WritableAccount},
        clock::{Epoch, Slot, UnixTimestamp},
        feature_set::{self, filter_votes_outside_slot_hashes, FeatureSet},
        hash::Hash,
        instruction::InstructionError,
        pubkey::Pubkey,
        rent::Rent,
        slot_hashes::SlotHash,
        sysvar::clock::Clock,
        transaction_context::{
            BorrowedAccount, IndexOfAccount, InstructionContext, TransactionContext,
        },
    },
    std::{
        cmp::Ordering,
        collections::{HashSet, VecDeque},
        fmt::Debug,
    },
};

#[frozen_abi(digest = "2AuJFjx7SYrJ2ugCfH1jFh3Lr9UHMEPfKwwk1NcjqND1")]
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, AbiEnumVisitor, AbiExample)]
pub enum VoteTransaction {
    Vote(Vote),
    VoteStateUpdate(VoteStateUpdate),
    #[serde(with = "serde_compact_vote_state_update")]
    CompactVoteStateUpdate(VoteStateUpdate),
}

impl VoteTransaction {
    pub fn slots(&self) -> Vec<Slot> {
        match self {
            VoteTransaction::Vote(vote) => vote.slots.clone(),
            VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.slots(),
            VoteTransaction::CompactVoteStateUpdate(vote_state_update) => vote_state_update.slots(),
        }
    }

    pub fn slot(&self, i: usize) -> Slot {
        match self {
            VoteTransaction::Vote(vote) => vote.slots[i],
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.lockouts[i].slot
            }
        }
    }

    pub fn len(&self) -> usize {
        match self {
            VoteTransaction::Vote(vote) => vote.slots.len(),
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.lockouts.len()
            }
        }
    }

    pub fn is_empty(&self) -> bool {
        match self {
            VoteTransaction::Vote(vote) => vote.slots.is_empty(),
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.lockouts.is_empty()
            }
        }
    }

    pub fn hash(&self) -> Hash {
        match self {
            VoteTransaction::Vote(vote) => vote.hash,
            VoteTransaction::VoteStateUpdate(vote_state_update) => vote_state_update.hash,
            VoteTransaction::CompactVoteStateUpdate(vote_state_update) => vote_state_update.hash,
        }
    }

    pub fn timestamp(&self) -> Option<UnixTimestamp> {
        match self {
            VoteTransaction::Vote(vote) => vote.timestamp,
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.timestamp
            }
        }
    }

    pub fn set_timestamp(&mut self, ts: Option<UnixTimestamp>) {
        match self {
            VoteTransaction::Vote(vote) => vote.timestamp = ts,
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.timestamp = ts
            }
        }
    }

    pub fn last_voted_slot(&self) -> Option<Slot> {
        match self {
            VoteTransaction::Vote(vote) => vote.last_voted_slot(),
            VoteTransaction::VoteStateUpdate(vote_state_update)
            | VoteTransaction::CompactVoteStateUpdate(vote_state_update) => {
                vote_state_update.last_voted_slot()
            }
        }
    }

    pub fn last_voted_slot_hash(&self) -> Option<(Slot, Hash)> {
        Some((self.last_voted_slot()?, self.hash()))
    }
}

impl From<Vote> for VoteTransaction {
    fn from(vote: Vote) -> Self {
        VoteTransaction::Vote(vote)
    }
}

impl From<VoteStateUpdate> for VoteTransaction {
    fn from(vote_state_update: VoteStateUpdate) -> Self {
        VoteTransaction::VoteStateUpdate(vote_state_update)
    }
}

// utility function, used by Stakes, tests
pub fn from<T: ReadableAccount>(account: &T) -> Option<VoteState> {
    VoteState::deserialize(account.data()).ok()
}

// utility function, used by Stakes, tests
pub fn to<T: WritableAccount>(versioned: &VoteStateVersions, account: &mut T) -> Option<()> {
    VoteState::serialize(versioned, account.data_as_mut_slice()).ok()
}

fn check_update_vote_state_slots_are_valid(
    vote_state: &VoteState,
    vote_state_update: &mut VoteStateUpdate,
    slot_hashes: &[(Slot, Hash)],
    feature_set: Option<&FeatureSet>,
) -> Result<(), VoteError> {
    if vote_state_update.lockouts.is_empty() {
        return Err(VoteError::EmptySlots);
    }

    // If the vote state update is not new enough, return
    if let Some(last_vote_slot) = vote_state.votes.back().map(|lockout| lockout.slot) {
        if vote_state_update.lockouts.back().unwrap().slot <= last_vote_slot {
            return Err(VoteError::VoteTooOld);
        }
    }

    let last_vote_state_update_slot = vote_state_update
        .lockouts
        .back()
        .expect("must be nonempty, checked above")
        .slot;

    if slot_hashes.is_empty() {
        return Err(VoteError::SlotsMismatch);
    }
    let earliest_slot_hash_in_history = slot_hashes.last().unwrap().0;

    // Check if the proposed vote is too old to be in the SlotHash history
    if last_vote_state_update_slot < earliest_slot_hash_in_history {
        // If this is the last slot in the vote update, it must be in SlotHashes,
        // otherwise we have no way of confirming if the hash matches
        return Err(VoteError::VoteTooOld);
    }

    // Check if the proposed root is too old
    let is_root_fix_enabled = feature_set
        .map(|feature_set| feature_set.is_active(&feature_set::vote_state_update_root_fix::id()))
        .unwrap_or(false);

    let original_proposed_root = vote_state_update.root;
    if let Some(new_proposed_root) = original_proposed_root {
        // If the new proposed root `R` is less than the earliest slot hash in the history
        // such that we cannot verify whether the slot was actually was on this fork, set
        // the root to the latest vote in the current vote that's less than R.
        if earliest_slot_hash_in_history > new_proposed_root {
            vote_state_update.root = vote_state.root_slot;
            if is_root_fix_enabled {
                let mut prev_slot = Slot::MAX;
                let current_root = vote_state_update.root;
                for lockout in vote_state.votes.iter().rev() {
                    let is_slot_bigger_than_root = current_root
                        .map(|current_root| lockout.slot > current_root)
                        .unwrap_or(true);
                    // Ensure we're iterating from biggest to smallest vote in the
                    // current vote state
                    assert!(lockout.slot < prev_slot && is_slot_bigger_than_root);
                    if lockout.slot <= new_proposed_root {
                        vote_state_update.root = Some(lockout.slot);
                        break;
                    }
                    prev_slot = lockout.slot;
                }
            }
        }
    }

    // Index into the new proposed vote state's slots, starting with the root if it exists then
    // we use this mutable root to fold checking the root slot into the below loop
    // for performance
    let mut root_to_check = vote_state_update.root;
    let mut vote_state_update_index = 0;

    // index into the slot_hashes, starting at the oldest known
    // slot hash
    let mut slot_hashes_index = slot_hashes.len();

    let mut vote_state_update_indexes_to_filter = vec![];

    // Note:
    //
    // 1) `vote_state_update.lockouts` is sorted from oldest/smallest vote to newest/largest
    // vote, due to the way votes are applied to the vote state (newest votes
    // pushed to the back).
    //
    // 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
    // the oldest/smallest vote
    //
    // Unlike for vote updates, vote state updates here can't only check votes older than the last vote
    // because have to ensure that every slot is actually part of the history, not just the most
    // recent ones
    while vote_state_update_index < vote_state_update.lockouts.len() && slot_hashes_index > 0 {
        let proposed_vote_slot = if let Some(root) = root_to_check {
            root
        } else {
            vote_state_update.lockouts[vote_state_update_index].slot
        };
        if root_to_check.is_none()
            && vote_state_update_index > 0
            && proposed_vote_slot <= vote_state_update.lockouts[vote_state_update_index - 1].slot
        {
            return Err(VoteError::SlotsNotOrdered);
        }
        let ancestor_slot = slot_hashes[slot_hashes_index - 1].0;

        // Find if this slot in the proposed vote state exists in the SlotHashes history
        // to confirm if it was a valid ancestor on this fork
        match proposed_vote_slot.cmp(&ancestor_slot) {
            Ordering::Less => {
                if slot_hashes_index == slot_hashes.len() {
                    // The vote slot does not exist in the SlotHashes history because it's too old,
                    // i.e. older than the oldest slot in the history.
                    assert!(proposed_vote_slot < earliest_slot_hash_in_history);
                    if !vote_state.contains_slot(proposed_vote_slot) && root_to_check.is_none() {
                        // If the vote slot is both:
                        // 1) Too old
                        // 2) Doesn't already exist in vote state
                        //
                        // Then filter it out
                        vote_state_update_indexes_to_filter.push(vote_state_update_index);
                    }
                    if let Some(new_proposed_root) = root_to_check {
                        if is_root_fix_enabled {
                            // 1. Because `root_to_check.is_some()`, then we know that
                            // we haven't checked the root yet in this loop, so
                            // `proposed_vote_slot` == `new_proposed_root` == `vote_state_update.root`.
                            assert_eq!(new_proposed_root, proposed_vote_slot);
                            // 2. We know from the assert earlier in the function that
                            // `proposed_vote_slot < earliest_slot_hash_in_history`,
                            // so from 1. we know that `new_proposed_root < earliest_slot_hash_in_history`.
                            assert!(new_proposed_root < earliest_slot_hash_in_history);
                        } else {
                            // If the vote state update has a root < earliest_slot_hash_in_history
                            // then we use the current root. The only case where this can happen
                            // is if the current root itself is not in slot hashes.
                            assert!(vote_state.root_slot.unwrap() < earliest_slot_hash_in_history);
                        }
                        root_to_check = None;
                    } else {
                        vote_state_update_index += 1;
                    }
                    continue;
                } else {
                    // If the vote slot is new enough to be in the slot history,
                    // but is not part of the slot history, then it must belong to another fork,
                    // which means this vote state update is invalid.
                    if root_to_check.is_some() {
                        return Err(VoteError::RootOnDifferentFork);
                    } else {
                        return Err(VoteError::SlotsMismatch);
                    }
                }
            }
            Ordering::Greater => {
                // Decrement `slot_hashes_index` to find newer slots in the SlotHashes history
                slot_hashes_index -= 1;
                continue;
            }
            Ordering::Equal => {
                // Once the slot in `vote_state_update.lockouts` is found, bump to the next slot
                // in `vote_state_update.lockouts` and continue. If we were checking the root,
                // start checking the vote state instead.
                if root_to_check.is_some() {
                    root_to_check = None;
                } else {
                    vote_state_update_index += 1;
                    slot_hashes_index -= 1;
                }
            }
        }
    }

    if vote_state_update_index != vote_state_update.lockouts.len() {
        // The last vote slot in the update did not exist in SlotHashes
        return Err(VoteError::SlotsMismatch);
    }

    // This assertion must be true at this point because we can assume by now:
    // 1) vote_state_update_index == vote_state_update.lockouts.len()
    // 2) last_vote_state_update_slot >= earliest_slot_hash_in_history
    // 3) !vote_state_update.lockouts.is_empty()
    //
    // 1) implies that during the last iteration of the loop above,
    // `vote_state_update_index` was equal to `vote_state_update.lockouts.len() - 1`,
    // and was then incremented to `vote_state_update.lockouts.len()`.
    // This means in that last loop iteration,
    // `proposed_vote_slot ==
    //  vote_state_update.lockouts[vote_state_update.lockouts.len() - 1] ==
    //  last_vote_state_update_slot`.
    //
    // Then we know the last comparison `match proposed_vote_slot.cmp(&ancestor_slot)`
    // is equivalent to `match last_vote_state_update_slot.cmp(&ancestor_slot)`. The result
    // of this match to increment `vote_state_update_index` must have been either:
    //
    // 1) The Equal case ran, in which case then we know this assertion must be true
    // 2) The Less case ran, and more specifically the case
    // `proposed_vote_slot < earliest_slot_hash_in_history` ran, which is equivalent to
    // `last_vote_state_update_slot < earliest_slot_hash_in_history`, but this is impossible
    // due to assumption 3) above.
    assert_eq!(
        last_vote_state_update_slot,
        slot_hashes[slot_hashes_index].0
    );

    if slot_hashes[slot_hashes_index].1 != vote_state_update.hash {
        // This means the newest vote in the slot has a match that
        // doesn't match the expected hash for that slot on this
        // fork
        warn!(
            "{} dropped vote {:?} failed to match hash {} {}",
            vote_state.node_pubkey,
            vote_state_update,
            vote_state_update.hash,
            slot_hashes[slot_hashes_index].1
        );
        inc_new_counter_info!("dropped-vote-hash", 1);
        return Err(VoteError::SlotHashMismatch);
    }

    // Filter out the irrelevant votes
    let mut vote_state_update_index = 0;
    let mut filter_votes_index = 0;
    vote_state_update.lockouts.retain(|_lockout| {
        let should_retain = if filter_votes_index == vote_state_update_indexes_to_filter.len() {
            true
        } else if vote_state_update_index == vote_state_update_indexes_to_filter[filter_votes_index]
        {
            filter_votes_index += 1;
            false
        } else {
            true
        };

        vote_state_update_index += 1;
        should_retain
    });

    Ok(())
}

fn check_slots_are_valid(
    vote_state: &VoteState,
    vote_slots: &[Slot],
    vote_hash: &Hash,
    slot_hashes: &[(Slot, Hash)],
) -> Result<(), VoteError> {
    // index into the vote's slots, starting at the oldest
    // slot
    let mut i = 0;

    // index into the slot_hashes, starting at the oldest known
    // slot hash
    let mut j = slot_hashes.len();

    // Note:
    //
    // 1) `vote_slots` is sorted from oldest/smallest vote to newest/largest
    // vote, due to the way votes are applied to the vote state (newest votes
    // pushed to the back).
    //
    // 2) Conversely, `slot_hashes` is sorted from newest/largest vote to
    // the oldest/smallest vote
    while i < vote_slots.len() && j > 0 {
        // 1) increment `i` to find the smallest slot `s` in `vote_slots`
        // where `s` >= `last_voted_slot`
        if vote_state
            .last_voted_slot()
            .map_or(false, |last_voted_slot| vote_slots[i] <= last_voted_slot)
        {
            i += 1;
            continue;
        }

        // 2) Find the hash for this slot `s`.
        if vote_slots[i] != slot_hashes[j - 1].0 {
            // Decrement `j` to find newer slots
            j -= 1;
            continue;
        }

        // 3) Once the hash for `s` is found, bump `s` to the next slot
        // in `vote_slots` and continue.
        i += 1;
        j -= 1;
    }

    if j == slot_hashes.len() {
        // This means we never made it to steps 2) or 3) above, otherwise
        // `j` would have been decremented at least once. This means
        // there are not slots in `vote_slots` greater than `last_voted_slot`
        debug!(
            "{} dropped vote slots {:?}, vote hash: {:?} slot hashes:SlotHash {:?}, too old ",
            vote_state.node_pubkey, vote_slots, vote_hash, slot_hashes
        );
        return Err(VoteError::VoteTooOld);
    }
    if i != vote_slots.len() {
        // This means there existed some slot for which we couldn't find
        // a matching slot hash in step 2)
        info!(
            "{} dropped vote slots {:?} failed to match slot hashes: {:?}",
            vote_state.node_pubkey, vote_slots, slot_hashes,
        );
        inc_new_counter_info!("dropped-vote-slot", 1);
        return Err(VoteError::SlotsMismatch);
    }
    if &slot_hashes[j].1 != vote_hash {
        // This means the newest slot in the `vote_slots` has a match that
        // doesn't match the expected hash for that slot on this
        // fork
        warn!(
            "{} dropped vote slots {:?} failed to match hash {} {}",
            vote_state.node_pubkey, vote_slots, vote_hash, slot_hashes[j].1
        );
        inc_new_counter_info!("dropped-vote-hash", 1);
        return Err(VoteError::SlotHashMismatch);
    }
    Ok(())
}

//Ensure `check_update_vote_state_slots_are_valid(&)` runs on the slots in `new_state`
// before `process_new_vote_state()` is called

// This function should guarantee the following about `new_state`:
//
// 1) It's well ordered, i.e. the slots are sorted from smallest to largest,
// and the confirmations sorted from largest to smallest.
// 2) Confirmations `c` on any vote slot satisfy `0 < c <= MAX_LOCKOUT_HISTORY`
// 3) Lockouts are not expired by consecutive votes, i.e. for every consecutive
// `v_i`, `v_{i + 1}` satisfy `v_i.last_locked_out_slot() >= v_{i + 1}`.

// We also guarantee that compared to the current vote state, `new_state`
// introduces no rollback. This means:
//
// 1) The last slot in `new_state` is always greater than any slot in the
// current vote state.
//
// 2) From 1), this means that for every vote `s` in the current state:
//    a) If there exists an `s'` in `new_state` where `s.slot == s'.slot`, then
//    we must guarantee `s.confirmations <= s'.confirmations`
//
//    b) If there does not exist any such `s'` in `new_state`, then there exists
//    some `t` that is the smallest vote in `new_state` where `t.slot > s.slot`.
//    `t` must have expired/popped off s', so it must be guaranteed that
//    `s.last_locked_out_slot() < t`.

// Note these two above checks do not guarantee that the vote state being submitted
// is a vote state that could have been created by iteratively building a tower
// by processing one vote at a time. For instance, the tower:
//
// { slot 0, confirmations: 31 }
// { slot 1, confirmations: 30 }
//
// is a legal tower that could be submitted on top of a previously empty tower. However,
// there is no way to create this tower from the iterative process, because slot 1 would
// have to have at least one other slot on top of it, even if the first 30 votes were all
// popped off.
pub fn process_new_vote_state(
    vote_state: &mut VoteState,
    new_state: VecDeque<Lockout>,
    new_root: Option<Slot>,
    timestamp: Option<i64>,
    epoch: Epoch,
    feature_set: Option<&FeatureSet>,
) -> Result<(), VoteError> {
    assert!(!new_state.is_empty());
    if new_state.len() > MAX_LOCKOUT_HISTORY {
        return Err(VoteError::TooManyVotes);
    }

    match (new_root, vote_state.root_slot) {
        (Some(new_root), Some(current_root)) => {
            if new_root < current_root {
                return Err(VoteError::RootRollBack);
            }
        }
        (None, Some(_)) => {
            return Err(VoteError::RootRollBack);
        }
        _ => (),
    }

    let mut previous_vote: Option<&Lockout> = None;

    // Check that all the votes in the new proposed state are:
    // 1) Strictly sorted from oldest to newest vote
    // 2) The confirmations are strictly decreasing
    // 3) Not zero confirmation votes
    for vote in &new_state {
        if vote.confirmation_count == 0 {
            return Err(VoteError::ZeroConfirmations);
        } else if vote.confirmation_count > MAX_LOCKOUT_HISTORY as u32 {
            return Err(VoteError::ConfirmationTooLarge);
        } else if let Some(new_root) = new_root {
            if vote.slot <= new_root
                &&
                // This check is necessary because
                // https://github.com/ryoqun/solana/blob/df55bfb46af039cbc597cd60042d49b9d90b5961/core/src/consensus.rs#L120
                // always sets a root for even empty towers, which is then hard unwrapped here
                // https://github.com/ryoqun/solana/blob/df55bfb46af039cbc597cd60042d49b9d90b5961/core/src/consensus.rs#L776
                new_root != Slot::default()
            {
                return Err(VoteError::SlotSmallerThanRoot);
            }
        }

        if let Some(previous_vote) = previous_vote {
            if previous_vote.slot >= vote.slot {
                return Err(VoteError::SlotsNotOrdered);
            } else if previous_vote.confirmation_count <= vote.confirmation_count {
                return Err(VoteError::ConfirmationsNotOrdered);
            } else if vote.slot > previous_vote.last_locked_out_slot() {
                return Err(VoteError::NewVoteStateLockoutMismatch);
            }
        }
        previous_vote = Some(vote);
    }

    // Find the first vote in the current vote state for a slot greater
    // than the new proposed root
    let mut current_vote_state_index = 0;
    let mut new_vote_state_index = 0;

    // Count the number of slots at and before the new root within the current vote state lockouts.  Start with 1
    // for the new root.  The purpose of this is to know how many slots were rooted by this state update:
    // - The new root was rooted
    // - As were any slots that were in the current state but are not in the new state.  The only slots which
    //   can be in this set are those oldest slots in the current vote state that are not present in the
    //   new vote state; these have been "popped off the back" of the tower and thus represent finalized slots
    let mut finalized_slot_count = 1_u64;

    if let Some(new_root) = new_root {
        for current_vote in &vote_state.votes {
            // Find the first vote in the current vote state for a slot greater
            // than the new proposed root
            if current_vote.slot <= new_root {
                current_vote_state_index += 1;
                if current_vote.slot != new_root {
                    finalized_slot_count += 1;
                }
                continue;
            }

            break;
        }
    }

    // All the votes in our current vote state that are missing from the new vote state
    // must have been expired by later votes. Check that the lockouts match this assumption.
    while current_vote_state_index < vote_state.votes.len()
        && new_vote_state_index < new_state.len()
    {
        let current_vote = &vote_state.votes[current_vote_state_index];
        let new_vote = &new_state[new_vote_state_index];

        // If the current slot is less than the new proposed slot, then the
        // new slot must have popped off the old slot, so check that the
        // lockouts are corrects.
        match current_vote.slot.cmp(&new_vote.slot) {
            Ordering::Less => {
                if current_vote.last_locked_out_slot() >= new_vote.slot {
                    return Err(VoteError::LockoutConflict);
                }
                current_vote_state_index += 1;
            }
            Ordering::Equal => {
                // The new vote state should never have less lockout than
                // the previous vote state for the same slot
                if new_vote.confirmation_count < current_vote.confirmation_count {
                    return Err(VoteError::ConfirmationRollBack);
                }

                current_vote_state_index += 1;
                new_vote_state_index += 1;
            }
            Ordering::Greater => {
                new_vote_state_index += 1;
            }
        }
    }

    // `new_vote_state` passed all the checks, finalize the change by rewriting
    // our state.
    if vote_state.root_slot != new_root {
        // Award vote credits based on the number of slots that were voted on and have reached finality
        if feature_set
            .map(|feature_set| {
                feature_set.is_active(&feature_set::vote_state_update_credit_per_dequeue::id())
            })
            .unwrap_or(false)
        {
            // For each finalized slot, there was one voted-on slot in the new vote state that was responsible for
            // finalizing it. Each of those votes is awarded 1 credit.
            vote_state.increment_credits(epoch, finalized_slot_count);
        } else {
            vote_state.increment_credits(epoch, 1);
        }
    }
    if let Some(timestamp) = timestamp {
        let last_slot = new_state.back().unwrap().slot;
        vote_state.process_timestamp(last_slot, timestamp)?;
    }
    vote_state.root_slot = new_root;
    vote_state.votes = new_state;
    Ok(())
}

pub fn process_vote(
    vote_state: &mut VoteState,
    vote: &Vote,
    slot_hashes: &[SlotHash],
    epoch: Epoch,
    feature_set: Option<&FeatureSet>,
) -> Result<(), VoteError> {
    if vote.slots.is_empty() {
        return Err(VoteError::EmptySlots);
    }
    let filtered_vote_slots = feature_set.and_then(|feature_set| {
        if feature_set.is_active(&filter_votes_outside_slot_hashes::id()) {
            let earliest_slot_in_history =
                slot_hashes.last().map(|(slot, _hash)| *slot).unwrap_or(0);
            Some(
                vote.slots
                    .iter()
                    .filter(|slot| **slot >= earliest_slot_in_history)
                    .cloned()
                    .collect::<Vec<Slot>>(),
            )
        } else {
            None
        }
    });

    let vote_slots = filtered_vote_slots.as_ref().unwrap_or(&vote.slots);
    if vote_slots.is_empty() {
        return Err(VoteError::VotesTooOldAllFiltered);
    }

    check_slots_are_valid(vote_state, vote_slots, &vote.hash, slot_hashes)?;

    vote_slots
        .iter()
        .for_each(|s| vote_state.process_next_vote_slot(*s, epoch));
    Ok(())
}

/// "unchecked" functions used by tests and Tower
pub fn process_vote_unchecked(vote_state: &mut VoteState, vote: Vote) {
    let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();
    let _ignored = process_vote(
        vote_state,
        &vote,
        &slot_hashes,
        vote_state.current_epoch(),
        None,
    );
}

#[cfg(test)]
pub fn process_slot_votes_unchecked(vote_state: &mut VoteState, slots: &[Slot]) {
    for slot in slots {
        process_slot_vote_unchecked(vote_state, *slot);
    }
}

pub fn process_slot_vote_unchecked(vote_state: &mut VoteState, slot: Slot) {
    process_vote_unchecked(vote_state, Vote::new(vec![slot], Hash::default()));
}

/// Authorize the given pubkey to withdraw or sign votes. This may be called multiple times,
/// but will implicitly withdraw authorization from the previously authorized
/// key
pub fn authorize<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    authorized: &Pubkey,
    vote_authorize: VoteAuthorize,
    signers: &HashSet<Pubkey, S>,
    clock: &Clock,
    feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
    let mut vote_state: VoteState = vote_account
        .get_state::<VoteStateVersions>()?
        .convert_to_current();

    match vote_authorize {
        VoteAuthorize::Voter => {
            let authorized_withdrawer_signer = if feature_set
                .is_active(&feature_set::vote_withdraw_authority_may_change_authorized_voter::id())
            {
                verify_authorized_signer(&vote_state.authorized_withdrawer, signers).is_ok()
            } else {
                false
            };

            vote_state.set_new_authorized_voter(
                authorized,
                clock.epoch,
                clock.leader_schedule_epoch + 1,
                |epoch_authorized_voter| {
                    // current authorized withdrawer or authorized voter must say "yay"
                    if authorized_withdrawer_signer {
                        Ok(())
                    } else {
                        verify_authorized_signer(&epoch_authorized_voter, signers)
                    }
                },
            )?;
        }
        VoteAuthorize::Withdrawer => {
            // current authorized withdrawer must say "yay"
            verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;
            vote_state.authorized_withdrawer = *authorized;
        }
    }

    vote_account.set_state(&VoteStateVersions::new_current(vote_state))
}

/// Update the node_pubkey, requires signature of the authorized voter
pub fn update_validator_identity<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    node_pubkey: &Pubkey,
    signers: &HashSet<Pubkey, S>,
) -> Result<(), InstructionError> {
    let mut vote_state: VoteState = vote_account
        .get_state::<VoteStateVersions>()?
        .convert_to_current();

    // current authorized withdrawer must say "yay"
    verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;

    // new node must say "yay"
    verify_authorized_signer(node_pubkey, signers)?;

    vote_state.node_pubkey = *node_pubkey;

    vote_account.set_state(&VoteStateVersions::new_current(vote_state))
}

/// Update the vote account's commission
pub fn update_commission<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    commission: u8,
    signers: &HashSet<Pubkey, S>,
) -> Result<(), InstructionError> {
    let mut vote_state: VoteState = vote_account
        .get_state::<VoteStateVersions>()?
        .convert_to_current();

    // current authorized withdrawer must say "yay"
    verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;

    vote_state.commission = commission;

    vote_account.set_state(&VoteStateVersions::new_current(vote_state))
}

fn verify_authorized_signer<S: std::hash::BuildHasher>(
    authorized: &Pubkey,
    signers: &HashSet<Pubkey, S>,
) -> Result<(), InstructionError> {
    if signers.contains(authorized) {
        Ok(())
    } else {
        Err(InstructionError::MissingRequiredSignature)
    }
}

/// Withdraw funds from the vote account
pub fn withdraw<S: std::hash::BuildHasher>(
    transaction_context: &TransactionContext,
    instruction_context: &InstructionContext,
    vote_account_index: IndexOfAccount,
    lamports: u64,
    to_account_index: IndexOfAccount,
    signers: &HashSet<Pubkey, S>,
    rent_sysvar: &Rent,
    clock: Option<&Clock>,
) -> Result<(), InstructionError> {
    let mut vote_account = instruction_context
        .try_borrow_instruction_account(transaction_context, vote_account_index)?;
    let vote_state: VoteState = vote_account
        .get_state::<VoteStateVersions>()?
        .convert_to_current();

    verify_authorized_signer(&vote_state.authorized_withdrawer, signers)?;

    let remaining_balance = vote_account
        .get_lamports()
        .checked_sub(lamports)
        .ok_or(InstructionError::InsufficientFunds)?;

    if remaining_balance == 0 {
        let reject_active_vote_account_close = clock
            .zip(vote_state.epoch_credits.last())
            .map(|(clock, (last_epoch_with_credits, _, _))| {
                let current_epoch = clock.epoch;
                // if current_epoch - last_epoch_with_credits < 2 then the validator has received credits
                // either in the current epoch or the previous epoch. If it's >= 2 then it has been at least
                // one full epoch since the validator has received credits.
                current_epoch.saturating_sub(*last_epoch_with_credits) < 2
            })
            .unwrap_or(false);

        if reject_active_vote_account_close {
            datapoint_debug!("vote-account-close", ("reject-active", 1, i64));
            return Err(VoteError::ActiveVoteAccountClose.into());
        } else {
            // Deinitialize upon zero-balance
            datapoint_debug!("vote-account-close", ("allow", 1, i64));
            vote_account.set_state(&VoteStateVersions::new_current(VoteState::default()))?;
        }
    } else {
        let min_rent_exempt_balance = rent_sysvar.minimum_balance(vote_account.get_data().len());
        if remaining_balance < min_rent_exempt_balance {
            return Err(InstructionError::InsufficientFunds);
        }
    }

    vote_account.checked_sub_lamports(lamports)?;
    drop(vote_account);
    let mut to_account = instruction_context
        .try_borrow_instruction_account(transaction_context, to_account_index)?;
    to_account.checked_add_lamports(lamports)?;
    Ok(())
}

/// Initialize the vote_state for a vote account
/// Assumes that the account is being init as part of a account creation or balance transfer and
/// that the transaction must be signed by the staker's keys
pub fn initialize_account<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    vote_init: &VoteInit,
    signers: &HashSet<Pubkey, S>,
    clock: &Clock,
) -> Result<(), InstructionError> {
    if vote_account.get_data().len() != VoteState::size_of() {
        return Err(InstructionError::InvalidAccountData);
    }
    let versioned = vote_account.get_state::<VoteStateVersions>()?;

    if !versioned.is_uninitialized() {
        return Err(InstructionError::AccountAlreadyInitialized);
    }

    // node must agree to accept this vote account
    verify_authorized_signer(&vote_init.node_pubkey, signers)?;

    vote_account.set_state(&VoteStateVersions::new_current(VoteState::new(
        vote_init, clock,
    )))
}

fn verify_and_get_vote_state<S: std::hash::BuildHasher>(
    vote_account: &BorrowedAccount,
    clock: &Clock,
    signers: &HashSet<Pubkey, S>,
) -> Result<VoteState, InstructionError> {
    let versioned = vote_account.get_state::<VoteStateVersions>()?;

    if versioned.is_uninitialized() {
        return Err(InstructionError::UninitializedAccount);
    }

    let mut vote_state = versioned.convert_to_current();
    let authorized_voter = vote_state.get_and_update_authorized_voter(clock.epoch)?;
    verify_authorized_signer(&authorized_voter, signers)?;

    Ok(vote_state)
}

pub fn process_vote_with_account<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    slot_hashes: &[SlotHash],
    clock: &Clock,
    vote: &Vote,
    signers: &HashSet<Pubkey, S>,
    feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
    let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;

    process_vote(
        &mut vote_state,
        vote,
        slot_hashes,
        clock.epoch,
        Some(feature_set),
    )?;
    if let Some(timestamp) = vote.timestamp {
        vote.slots
            .iter()
            .max()
            .ok_or(VoteError::EmptySlots)
            .and_then(|slot| vote_state.process_timestamp(*slot, timestamp))?;
    }
    vote_account.set_state(&VoteStateVersions::new_current(vote_state))
}

pub fn process_vote_state_update<S: std::hash::BuildHasher>(
    vote_account: &mut BorrowedAccount,
    slot_hashes: &[SlotHash],
    clock: &Clock,
    vote_state_update: VoteStateUpdate,
    signers: &HashSet<Pubkey, S>,
    feature_set: &FeatureSet,
) -> Result<(), InstructionError> {
    let mut vote_state = verify_and_get_vote_state(vote_account, clock, signers)?;
    do_process_vote_state_update(
        &mut vote_state,
        slot_hashes,
        clock.epoch,
        vote_state_update,
        Some(feature_set),
    )?;
    vote_account.set_state(&VoteStateVersions::new_current(vote_state))
}

pub fn do_process_vote_state_update(
    vote_state: &mut VoteState,
    slot_hashes: &[SlotHash],
    epoch: u64,
    mut vote_state_update: VoteStateUpdate,
    feature_set: Option<&FeatureSet>,
) -> Result<(), VoteError> {
    check_update_vote_state_slots_are_valid(
        vote_state,
        &mut vote_state_update,
        slot_hashes,
        feature_set,
    )?;
    process_new_vote_state(
        vote_state,
        vote_state_update.lockouts,
        vote_state_update.root,
        vote_state_update.timestamp,
        epoch,
        feature_set,
    )
}

pub fn create_account_with_authorized(
    node_pubkey: &Pubkey,
    authorized_voter: &Pubkey,
    authorized_withdrawer: &Pubkey,
    commission: u8,
    lamports: u64,
) -> AccountSharedData {
    let mut vote_account = AccountSharedData::new(lamports, VoteState::size_of(), &id());

    let vote_state = VoteState::new(
        &VoteInit {
            node_pubkey: *node_pubkey,
            authorized_voter: *authorized_voter,
            authorized_withdrawer: *authorized_withdrawer,
            commission,
        },
        &Clock::default(),
    );

    let versioned = VoteStateVersions::new_current(vote_state);
    VoteState::serialize(&versioned, vote_account.data_as_mut_slice()).unwrap();

    vote_account
}

// create_account() should be removed, use create_account_with_authorized() instead
pub fn create_account(
    vote_pubkey: &Pubkey,
    node_pubkey: &Pubkey,
    commission: u8,
    lamports: u64,
) -> AccountSharedData {
    create_account_with_authorized(node_pubkey, vote_pubkey, vote_pubkey, commission, lamports)
}

#[cfg(test)]
mod tests {
    use {
        super::*,
        crate::vote_state,
        solana_sdk::{account::AccountSharedData, account_utils::StateMut, hash::hash},
        std::cell::RefCell,
    };

    const MAX_RECENT_VOTES: usize = 16;

    fn vote_state_new_for_test(auth_pubkey: &Pubkey) -> VoteState {
        VoteState::new(
            &VoteInit {
                node_pubkey: solana_sdk::pubkey::new_rand(),
                authorized_voter: *auth_pubkey,
                authorized_withdrawer: *auth_pubkey,
                commission: 0,
            },
            &Clock::default(),
        )
    }

    fn create_test_account() -> (Pubkey, RefCell<AccountSharedData>) {
        let rent = Rent::default();
        let balance = VoteState::get_rent_exempt_reserve(&rent);
        let vote_pubkey = solana_sdk::pubkey::new_rand();
        (
            vote_pubkey,
            RefCell::new(vote_state::create_account(
                &vote_pubkey,
                &solana_sdk::pubkey::new_rand(),
                0,
                balance,
            )),
        )
    }

    #[test]
    fn test_vote_lockout() {
        let (_vote_pubkey, vote_account) = create_test_account();

        let mut vote_state: VoteState =
            StateMut::<VoteStateVersions>::state(&*vote_account.borrow())
                .unwrap()
                .convert_to_current();

        for i in 0..(MAX_LOCKOUT_HISTORY + 1) {
            process_slot_vote_unchecked(&mut vote_state, (INITIAL_LOCKOUT * i) as u64);
        }

        // The last vote should have been popped b/c it reached a depth of MAX_LOCKOUT_HISTORY
        assert_eq!(vote_state.votes.len(), MAX_LOCKOUT_HISTORY);
        assert_eq!(vote_state.root_slot, Some(0));
        check_lockouts(&vote_state);

        // One more vote that confirms the entire stack,
        // the root_slot should change to the
        // second vote
        let top_vote = vote_state.votes.front().unwrap().slot;
        let slot = vote_state.last_lockout().unwrap().last_locked_out_slot();
        process_slot_vote_unchecked(&mut vote_state, slot);
        assert_eq!(Some(top_vote), vote_state.root_slot);

        // Expire everything except the first vote
        let slot = vote_state.votes.front().unwrap().last_locked_out_slot();
        process_slot_vote_unchecked(&mut vote_state, slot);
        // First vote and new vote are both stored for a total of 2 votes
        assert_eq!(vote_state.votes.len(), 2);
    }

    #[test]
    fn test_vote_double_lockout_after_expiration() {
        let voter_pubkey = solana_sdk::pubkey::new_rand();
        let mut vote_state = vote_state_new_for_test(&voter_pubkey);

        for i in 0..3 {
            process_slot_vote_unchecked(&mut vote_state, i as u64);
        }

        check_lockouts(&vote_state);

        // Expire the third vote (which was a vote for slot 2). The height of the
        // vote stack is unchanged, so none of the previous votes should have
        // doubled in lockout
        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 1) as u64);
        check_lockouts(&vote_state);

        // Vote again, this time the vote stack depth increases, so the votes should
        // double for everybody
        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 2) as u64);
        check_lockouts(&vote_state);

        // Vote again, this time the vote stack depth increases, so the votes should
        // double for everybody
        process_slot_vote_unchecked(&mut vote_state, (2 + INITIAL_LOCKOUT + 3) as u64);
        check_lockouts(&vote_state);
    }

    #[test]
    fn test_expire_multiple_votes() {
        let voter_pubkey = solana_sdk::pubkey::new_rand();
        let mut vote_state = vote_state_new_for_test(&voter_pubkey);

        for i in 0..3 {
            process_slot_vote_unchecked(&mut vote_state, i as u64);
        }

        assert_eq!(vote_state.votes[0].confirmation_count, 3);

        // Expire the second and third votes
        let expire_slot = vote_state.votes[1].slot + vote_state.votes[1].lockout() + 1;
        process_slot_vote_unchecked(&mut vote_state, expire_slot);
        assert_eq!(vote_state.votes.len(), 2);

        // Check that the old votes expired
        assert_eq!(vote_state.votes[0].slot, 0);
        assert_eq!(vote_state.votes[1].slot, expire_slot);

        // Process one more vote
        process_slot_vote_unchecked(&mut vote_state, expire_slot + 1);

        // Confirmation count for the older first vote should remain unchanged
        assert_eq!(vote_state.votes[0].confirmation_count, 3);

        // The later votes should still have increasing confirmation counts
        assert_eq!(vote_state.votes[1].confirmation_count, 2);
        assert_eq!(vote_state.votes[2].confirmation_count, 1);
    }

    #[test]
    fn test_vote_credits() {
        let voter_pubkey = solana_sdk::pubkey::new_rand();
        let mut vote_state = vote_state_new_for_test(&voter_pubkey);

        for i in 0..MAX_LOCKOUT_HISTORY {
            process_slot_vote_unchecked(&mut vote_state, i as u64);
        }

        assert_eq!(vote_state.credits(), 0);

        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 1);
        assert_eq!(vote_state.credits(), 1);
        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 2);
        assert_eq!(vote_state.credits(), 2);
        process_slot_vote_unchecked(&mut vote_state, MAX_LOCKOUT_HISTORY as u64 + 3);
        assert_eq!(vote_state.credits(), 3);
    }

    #[test]
    fn test_duplicate_vote() {
        let voter_pubkey = solana_sdk::pubkey::new_rand();
        let mut vote_state = vote_state_new_for_test(&voter_pubkey);
        process_slot_vote_unchecked(&mut vote_state, 0);
        process_slot_vote_unchecked(&mut vote_state, 1);
        process_slot_vote_unchecked(&mut vote_state, 0);
        assert_eq!(vote_state.nth_recent_vote(0).unwrap().slot, 1);
        assert_eq!(vote_state.nth_recent_vote(1).unwrap().slot, 0);
        assert!(vote_state.nth_recent_vote(2).is_none());
    }

    #[test]
    fn test_nth_recent_vote() {
        let voter_pubkey = solana_sdk::pubkey::new_rand();
        let mut vote_state = vote_state_new_for_test(&voter_pubkey);
        for i in 0..MAX_LOCKOUT_HISTORY {
            process_slot_vote_unchecked(&mut vote_state, i as u64);
        }
        for i in 0..(MAX_LOCKOUT_HISTORY - 1) {
            assert_eq!(
                vote_state.nth_recent_vote(i).unwrap().slot as usize,
                MAX_LOCKOUT_HISTORY - i - 1,
            );
        }
        assert!(vote_state.nth_recent_vote(MAX_LOCKOUT_HISTORY).is_none());
    }

    fn check_lockouts(vote_state: &VoteState) {
        for (i, vote) in vote_state.votes.iter().enumerate() {
            let num_votes = vote_state.votes.len() - i;
            assert_eq!(vote.lockout(), INITIAL_LOCKOUT.pow(num_votes as u32) as u64);
        }
    }

    fn recent_votes(vote_state: &VoteState) -> Vec<Vote> {
        let start = vote_state.votes.len().saturating_sub(MAX_RECENT_VOTES);
        (start..vote_state.votes.len())
            .map(|i| Vote::new(vec![vote_state.votes.get(i).unwrap().slot], Hash::default()))
            .collect()
    }

    /// check that two accounts with different data can be brought to the same state with one vote submission
    #[test]
    fn test_process_missed_votes() {
        let account_a = solana_sdk::pubkey::new_rand();
        let mut vote_state_a = vote_state_new_for_test(&account_a);
        let account_b = solana_sdk::pubkey::new_rand();
        let mut vote_state_b = vote_state_new_for_test(&account_b);

        // process some votes on account a
        (0..5).for_each(|i| process_slot_vote_unchecked(&mut vote_state_a, i as u64));
        assert_ne!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));

        // as long as b has missed less than "NUM_RECENT" votes both accounts should be in sync
        let slots = (0u64..MAX_RECENT_VOTES as u64).collect();
        let vote = Vote::new(slots, Hash::default());
        let slot_hashes: Vec<_> = vote.slots.iter().rev().map(|x| (*x, vote.hash)).collect();

        assert_eq!(
            process_vote(
                &mut vote_state_a,
                &vote,
                &slot_hashes,
                0,
                Some(&FeatureSet::default())
            ),
            Ok(())
        );
        assert_eq!(
            process_vote(
                &mut vote_state_b,
                &vote,
                &slot_hashes,
                0,
                Some(&FeatureSet::default())
            ),
            Ok(())
        );
        assert_eq!(recent_votes(&vote_state_a), recent_votes(&vote_state_b));
    }

    #[test]
    fn test_process_vote_skips_old_vote() {
        let mut vote_state = VoteState::default();

        let vote = Vote::new(vec![0], Hash::default());
        let slot_hashes: Vec<_> = vec![(0, vote.hash)];
        assert_eq!(
            process_vote(
                &mut vote_state,
                &vote,
                &slot_hashes,
                0,
                Some(&FeatureSet::default())
            ),
            Ok(())
        );
        let recent = recent_votes(&vote_state);
        assert_eq!(
            process_vote(
                &mut vote_state,
                &vote,
                &slot_hashes,
                0,
                Some(&FeatureSet::default())
            ),
            Err(VoteError::VoteTooOld)
        );
        assert_eq!(recent, recent_votes(&vote_state));
    }

    #[test]
    fn test_check_slots_are_valid_vote_empty_slot_hashes() {
        let vote_state = VoteState::default();

        let vote = Vote::new(vec![0], Hash::default());
        assert_eq!(
            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &[]),
            Err(VoteError::VoteTooOld)
        );
    }

    #[test]
    fn test_check_slots_are_valid_new_vote() {
        let vote_state = VoteState::default();

        let vote = Vote::new(vec![0], Hash::default());
        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
        assert_eq!(
            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
            Ok(())
        );
    }

    #[test]
    fn test_check_slots_are_valid_bad_hash() {
        let vote_state = VoteState::default();

        let vote = Vote::new(vec![0], Hash::default());
        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), hash(vote.hash.as_ref()))];
        assert_eq!(
            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
            Err(VoteError::SlotHashMismatch)
        );
    }

    #[test]
    fn test_check_slots_are_valid_bad_slot() {
        let vote_state = VoteState::default();

        let vote = Vote::new(vec![1], Hash::default());
        let slot_hashes: Vec<_> = vec![(0, vote.hash)];
        assert_eq!(
            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
            Err(VoteError::SlotsMismatch)
        );
    }

    #[test]
    fn test_check_slots_are_valid_duplicate_vote() {
        let mut vote_state = VoteState::default();

        let vote = Vote::new(vec![0], Hash::default());
        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
        assert_eq!(
            process_vote(
                &mut vote_state,
                &vote,
                &slot_hashes,
                0,
                Some(&FeatureSet::default())
            ),
            Ok(())
        );
        assert_eq!(
            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
            Err(VoteError::VoteTooOld)
        );
    }

    #[test]
    fn test_check_slots_are_valid_next_vote() {
        let mut vote_state = VoteState::default();

        let vote = Vote::new(vec![0], Hash::default());
        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
        assert_eq!(
            process_vote(
                &mut vote_state,
                &vote,
                &slot_hashes,
                0,
                Some(&FeatureSet::default())
            ),
            Ok(())
        );

        let vote = Vote::new(vec![0, 1], Hash::default());
        let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
        assert_eq!(
            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
            Ok(())
        );
    }

    #[test]
    fn test_check_slots_are_valid_next_vote_only() {
        let mut vote_state = VoteState::default();

        let vote = Vote::new(vec![0], Hash::default());
        let slot_hashes: Vec<_> = vec![(*vote.slots.last().unwrap(), vote.hash)];
        assert_eq!(
            process_vote(
                &mut vote_state,
                &vote,
                &slot_hashes,
                0,
                Some(&FeatureSet::default())
            ),
            Ok(())
        );

        let vote = Vote::new(vec![1], Hash::default());
        let slot_hashes: Vec<_> = vec![(1, vote.hash), (0, vote.hash)];
        assert_eq!(
            check_slots_are_valid(&vote_state, &vote.slots, &vote.hash, &slot_hashes),
            Ok(())
        );
    }
    #[test]
    fn test_process_vote_empty_slots() {
        let mut vote_state = VoteState::default();

        let vote = Vote::new(vec![], Hash::default());
        assert_eq!(
            process_vote(&mut vote_state, &vote, &[], 0, Some(&FeatureSet::default())),
            Err(VoteError::EmptySlots)
        );
    }

    // Test vote credit updates after "one credit per slot" feature is enabled
    #[test]
    fn test_vote_state_update_increment_credits() {
        // Create a new Votestate
        let mut vote_state = VoteState::new(&VoteInit::default(), &Clock::default());

        // Test data: a sequence of groups of votes to simulate having been cast, after each group a vote
        // state update is compared to "normal" vote processing to ensure that credits are earned equally
        let test_vote_groups: Vec<Vec<Slot>> = vec![
            // Initial set of votes that don't dequeue any slots, so no credits earned
            vec![1, 2, 3, 4, 5, 6, 7, 8],
            vec![
                9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
                30, 31,
            ],
            // Now a single vote which should result in the first root and first credit earned
            vec![32],
            // Now another vote, should earn one credit
            vec![33],
            // Two votes in sequence
            vec![34, 35],
            // 3 votes in sequence
            vec![36, 37, 38],
            // 30 votes in sequence
            vec![
                39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
                60, 61, 62, 63, 64, 65, 66, 67, 68,
            ],
            // 31 votes in sequence
            vec![
                69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
                90, 91, 92, 93, 94, 95, 96, 97, 98, 99,
            ],
            // Votes with expiry
            vec![100, 101, 106, 107, 112, 116, 120, 121, 122, 124],
            // More votes with expiry of a large number of votes
            vec![200, 201],
            vec![
                202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217,
                218, 219, 220, 221, 222, 223, 224, 225, 226,
            ],
            vec![227, 228, 229, 230, 231, 232, 233, 234, 235, 236],
        ];

        let mut feature_set = FeatureSet::default();
        feature_set.activate(&feature_set::vote_state_update_credit_per_dequeue::id(), 1);

        for vote_group in test_vote_groups {
            // Duplicate vote_state so that the new vote can be applied
            let mut vote_state_after_vote = vote_state.clone();

            process_vote_unchecked(
                &mut vote_state_after_vote,
                Vote {
                    slots: vote_group.clone(),
                    hash: Hash::new_unique(),
                    timestamp: None,
                },
            );

            // Now use the resulting new vote state to perform a vote state update on vote_state
            assert_eq!(
                process_new_vote_state(
                    &mut vote_state,
                    vote_state_after_vote.votes,
                    vote_state_after_vote.root_slot,
                    None,
                    0,
                    Some(&feature_set)
                ),
                Ok(())
            );

            // And ensure that the credits earned were the same
            assert_eq!(
                vote_state.epoch_credits,
                vote_state_after_vote.epoch_credits
            );
        }
    }

    #[test]
    fn test_process_new_vote_too_many_votes() {
        let mut vote_state1 = VoteState::default();
        let bad_votes: VecDeque<Lockout> = (0..=MAX_LOCKOUT_HISTORY)
            .map(|slot| Lockout {
                slot: slot as Slot,
                confirmation_count: (MAX_LOCKOUT_HISTORY - slot + 1) as u32,
            })
            .collect();

        let current_epoch = vote_state1.current_epoch();
        assert_eq!(
            process_new_vote_state(&mut vote_state1, bad_votes, None, None, current_epoch, None,),
            Err(VoteError::TooManyVotes)
        );
    }

    #[test]
    fn test_process_new_vote_state_root_rollback() {
        let mut vote_state1 = VoteState::default();
        for i in 0..MAX_LOCKOUT_HISTORY + 2 {
            process_slot_vote_unchecked(&mut vote_state1, i as Slot);
        }
        assert_eq!(vote_state1.root_slot.unwrap(), 1);

        // Update vote_state2 with a higher slot so that `process_new_vote_state`
        // doesn't panic.
        let mut vote_state2 = vote_state1.clone();
        process_slot_vote_unchecked(&mut vote_state2, MAX_LOCKOUT_HISTORY as Slot + 3);

        // Trying to set a lesser root should error
        let lesser_root = Some(0);

        let current_epoch = vote_state2.current_epoch();
        assert_eq!(
            process_new_vote_state(
                &mut vote_state1,
                vote_state2.votes.clone(),
                lesser_root,
                None,
                current_epoch,
                None,
            ),
            Err(VoteError::RootRollBack)
        );

        // Trying to set root to None should error
        let none_root = None;
        assert_eq!(
            process_new_vote_state(
                &mut vote_state1,
                vote_state2.votes.clone(),
                none_root,
                None,
                current_epoch,
                None,
            ),
            Err(VoteError::RootRollBack)
        );
    }

    #[test]
    fn test_process_new_vote_state_zero_confirmations() {
        let mut vote_state1 = VoteState::default();
        let current_epoch = vote_state1.current_epoch();

        let bad_votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: 0,
                confirmation_count: 0,
            },
            Lockout {
                slot: 1,
                confirmation_count: 1,
            },
        ]
        .into_iter()
        .collect();
        assert_eq!(
            process_new_vote_state(&mut vote_state1, bad_votes, None, None, current_epoch, None,),
            Err(VoteError::ZeroConfirmations)
        );

        let bad_votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: 0,
                confirmation_count: 2,
            },
            Lockout {
                slot: 1,
                confirmation_count: 0,
            },
        ]
        .into_iter()
        .collect();
        assert_eq!(
            process_new_vote_state(&mut vote_state1, bad_votes, None, None, current_epoch, None,),
            Err(VoteError::ZeroConfirmations)
        );
    }

    #[test]
    fn test_process_new_vote_state_confirmations_too_large() {
        let mut vote_state1 = VoteState::default();
        let current_epoch = vote_state1.current_epoch();

        let good_votes: VecDeque<Lockout> = vec![Lockout {
            slot: 0,
            confirmation_count: MAX_LOCKOUT_HISTORY as u32,
        }]
        .into_iter()
        .collect();

        process_new_vote_state(
            &mut vote_state1,
            good_votes,
            None,
            None,
            current_epoch,
            None,
        )
        .unwrap();

        let mut vote_state1 = VoteState::default();
        let bad_votes: VecDeque<Lockout> = vec![Lockout {
            slot: 0,
            confirmation_count: MAX_LOCKOUT_HISTORY as u32 + 1,
        }]
        .into_iter()
        .collect();
        assert_eq!(
            process_new_vote_state(&mut vote_state1, bad_votes, None, None, current_epoch, None),
            Err(VoteError::ConfirmationTooLarge)
        );
    }

    #[test]
    fn test_process_new_vote_state_slot_smaller_than_root() {
        let mut vote_state1 = VoteState::default();
        let current_epoch = vote_state1.current_epoch();
        let root_slot = 5;

        let bad_votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: root_slot,
                confirmation_count: 2,
            },
            Lockout {
                slot: root_slot + 1,
                confirmation_count: 1,
            },
        ]
        .into_iter()
        .collect();
        assert_eq!(
            process_new_vote_state(
                &mut vote_state1,
                bad_votes,
                Some(root_slot),
                None,
                current_epoch,
                None,
            ),
            Err(VoteError::SlotSmallerThanRoot)
        );

        let bad_votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: root_slot - 1,
                confirmation_count: 2,
            },
            Lockout {
                slot: root_slot + 1,
                confirmation_count: 1,
            },
        ]
        .into_iter()
        .collect();
        assert_eq!(
            process_new_vote_state(
                &mut vote_state1,
                bad_votes,
                Some(root_slot),
                None,
                current_epoch,
                None,
            ),
            Err(VoteError::SlotSmallerThanRoot)
        );
    }

    #[test]
    fn test_process_new_vote_state_slots_not_ordered() {
        let mut vote_state1 = VoteState::default();
        let current_epoch = vote_state1.current_epoch();

        let bad_votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: 1,
                confirmation_count: 2,
            },
            Lockout {
                slot: 0,
                confirmation_count: 1,
            },
        ]
        .into_iter()
        .collect();
        assert_eq!(
            process_new_vote_state(&mut vote_state1, bad_votes, None, None, current_epoch, None),
            Err(VoteError::SlotsNotOrdered)
        );

        let bad_votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: 1,
                confirmation_count: 2,
            },
            Lockout {
                slot: 1,
                confirmation_count: 1,
            },
        ]
        .into_iter()
        .collect();
        assert_eq!(
            process_new_vote_state(&mut vote_state1, bad_votes, None, None, current_epoch, None),
            Err(VoteError::SlotsNotOrdered)
        );
    }

    #[test]
    fn test_process_new_vote_state_confirmations_not_ordered() {
        let mut vote_state1 = VoteState::default();
        let current_epoch = vote_state1.current_epoch();

        let bad_votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: 0,
                confirmation_count: 1,
            },
            Lockout {
                slot: 1,
                confirmation_count: 2,
            },
        ]
        .into_iter()
        .collect();
        assert_eq!(
            process_new_vote_state(&mut vote_state1, bad_votes, None, None, current_epoch, None),
            Err(VoteError::ConfirmationsNotOrdered)
        );

        let bad_votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: 0,
                confirmation_count: 1,
            },
            Lockout {
                slot: 1,
                confirmation_count: 1,
            },
        ]
        .into_iter()
        .collect();
        assert_eq!(
            process_new_vote_state(&mut vote_state1, bad_votes, None, None, current_epoch, None),
            Err(VoteError::ConfirmationsNotOrdered)
        );
    }

    #[test]
    fn test_process_new_vote_state_new_vote_state_lockout_mismatch() {
        let mut vote_state1 = VoteState::default();
        let current_epoch = vote_state1.current_epoch();

        let bad_votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: 0,
                confirmation_count: 2,
            },
            Lockout {
                slot: 7,
                confirmation_count: 1,
            },
        ]
        .into_iter()
        .collect();

        // Slot 7 should have expired slot 0
        assert_eq!(
            process_new_vote_state(&mut vote_state1, bad_votes, None, None, current_epoch, None,),
            Err(VoteError::NewVoteStateLockoutMismatch)
        );
    }

    #[test]
    fn test_process_new_vote_state_confirmation_rollback() {
        let mut vote_state1 = VoteState::default();
        let current_epoch = vote_state1.current_epoch();
        let votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: 0,
                confirmation_count: 4,
            },
            Lockout {
                slot: 1,
                confirmation_count: 3,
            },
        ]
        .into_iter()
        .collect();
        process_new_vote_state(&mut vote_state1, votes, None, None, current_epoch, None).unwrap();

        let votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: 0,
                confirmation_count: 4,
            },
            Lockout {
                slot: 1,
                // Confirmation count lowered illegally
                confirmation_count: 2,
            },
            Lockout {
                slot: 2,
                confirmation_count: 1,
            },
        ]
        .into_iter()
        .collect();
        // Should error because newer vote state should not have lower confirmation the same slot
        // 1
        assert_eq!(
            process_new_vote_state(&mut vote_state1, votes, None, None, current_epoch, None),
            Err(VoteError::ConfirmationRollBack)
        );
    }

    #[test]
    fn test_process_new_vote_state_root_progress() {
        let mut vote_state1 = VoteState::default();
        for i in 0..MAX_LOCKOUT_HISTORY {
            process_slot_vote_unchecked(&mut vote_state1, i as u64);
        }

        assert!(vote_state1.root_slot.is_none());
        let mut vote_state2 = vote_state1.clone();

        // 1) Try to update `vote_state1` with no root,
        // to `vote_state2`, which has a new root, should succeed.
        //
        // 2) Then try to update`vote_state1` with an existing root,
        // to `vote_state2`, which has a newer root, which
        // should succeed.
        for new_vote in MAX_LOCKOUT_HISTORY + 1..=MAX_LOCKOUT_HISTORY + 2 {
            process_slot_vote_unchecked(&mut vote_state2, new_vote as Slot);
            assert_ne!(vote_state1.root_slot, vote_state2.root_slot);

            process_new_vote_state(
                &mut vote_state1,
                vote_state2.votes.clone(),
                vote_state2.root_slot,
                None,
                vote_state2.current_epoch(),
                None,
            )
            .unwrap();

            assert_eq!(vote_state1, vote_state2);
        }
    }

    #[test]
    fn test_process_new_vote_state_same_slot_but_not_common_ancestor() {
        // It might be possible that during the switch from old vote instructions
        // to new vote instructions, new_state contains votes for slots LESS
        // than the current state, for instance:
        //
        // Current on-chain state: 1, 5
        // New state: 1, 2 (lockout: 4), 3, 5, 7
        //
        // Imagine the validator made two of these votes:
        // 1) The first vote {1, 2, 3} didn't land in the old state, but didn't
        // land on chain
        // 2) A second vote {1, 2, 5} was then submitted, which landed
        //
        //
        // 2 is not popped off in the local tower because 3 doubled the lockout.
        // However, 3 did not land in the on-chain state, so the vote {1, 2, 6}
        // will immediately pop off 2.

        // Construct on-chain vote state
        let mut vote_state1 = VoteState::default();
        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5]);
        assert_eq!(
            vote_state1
                .votes
                .iter()
                .map(|vote| vote.slot)
                .collect::<Vec<Slot>>(),
            vec![1, 5]
        );

        // Construct local tower state
        let mut vote_state2 = VoteState::default();
        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
        assert_eq!(
            vote_state2
                .votes
                .iter()
                .map(|vote| vote.slot)
                .collect::<Vec<Slot>>(),
            vec![1, 2, 3, 5, 7]
        );

        // See that on-chain vote state can update properly
        process_new_vote_state(
            &mut vote_state1,
            vote_state2.votes.clone(),
            vote_state2.root_slot,
            None,
            vote_state2.current_epoch(),
            None,
        )
        .unwrap();

        assert_eq!(vote_state1, vote_state2);
    }

    #[test]
    fn test_process_new_vote_state_lockout_violation() {
        // Construct on-chain vote state
        let mut vote_state1 = VoteState::default();
        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 4, 5]);
        assert_eq!(
            vote_state1
                .votes
                .iter()
                .map(|vote| vote.slot)
                .collect::<Vec<Slot>>(),
            vec![1, 2, 4, 5]
        );

        // Construct conflicting tower state. Vote 4 is missing,
        // but 5 should not have popped off vote 4.
        let mut vote_state2 = VoteState::default();
        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 7]);
        assert_eq!(
            vote_state2
                .votes
                .iter()
                .map(|vote| vote.slot)
                .collect::<Vec<Slot>>(),
            vec![1, 2, 3, 5, 7]
        );

        // See that on-chain vote state can update properly
        assert_eq!(
            process_new_vote_state(
                &mut vote_state1,
                vote_state2.votes.clone(),
                vote_state2.root_slot,
                None,
                vote_state2.current_epoch(),
                None
            ),
            Err(VoteError::LockoutConflict)
        );
    }

    #[test]
    fn test_process_new_vote_state_lockout_violation2() {
        // Construct on-chain vote state
        let mut vote_state1 = VoteState::default();
        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 5, 6, 7]);
        assert_eq!(
            vote_state1
                .votes
                .iter()
                .map(|vote| vote.slot)
                .collect::<Vec<Slot>>(),
            vec![1, 5, 6, 7]
        );

        // Construct a new vote state. Violates on-chain state because 8
        // should not have popped off 7
        let mut vote_state2 = VoteState::default();
        process_slot_votes_unchecked(&mut vote_state2, &[1, 2, 3, 5, 6, 8]);
        assert_eq!(
            vote_state2
                .votes
                .iter()
                .map(|vote| vote.slot)
                .collect::<Vec<Slot>>(),
            vec![1, 2, 3, 5, 6, 8]
        );

        // Both vote states contain `5`, but `5` is not part of the common prefix
        // of both vote states. However, the violation should still be detected.
        assert_eq!(
            process_new_vote_state(
                &mut vote_state1,
                vote_state2.votes.clone(),
                vote_state2.root_slot,
                None,
                vote_state2.current_epoch(),
                None
            ),
            Err(VoteError::LockoutConflict)
        );
    }

    #[test]
    fn test_process_new_vote_state_expired_ancestor_not_removed() {
        // Construct on-chain vote state
        let mut vote_state1 = VoteState::default();
        process_slot_votes_unchecked(&mut vote_state1, &[1, 2, 3, 9]);
        assert_eq!(
            vote_state1
                .votes
                .iter()
                .map(|vote| vote.slot)
                .collect::<Vec<Slot>>(),
            vec![1, 9]
        );

        // Example: {1: lockout 8, 9: lockout 2}, vote on 10 will not pop off 1
        // because 9 is not popped off yet
        let mut vote_state2 = vote_state1.clone();
        process_slot_vote_unchecked(&mut vote_state2, 10);

        // Slot 1 has been expired by 10, but is kept alive by its descendant
        // 9 which has not been expired yet.
        assert_eq!(vote_state2.votes[0].slot, 1);
        assert_eq!(vote_state2.votes[0].last_locked_out_slot(), 9);
        assert_eq!(
            vote_state2
                .votes
                .iter()
                .map(|vote| vote.slot)
                .collect::<Vec<Slot>>(),
            vec![1, 9, 10]
        );

        // Should be able to update vote_state1
        process_new_vote_state(
            &mut vote_state1,
            vote_state2.votes.clone(),
            vote_state2.root_slot,
            None,
            vote_state2.current_epoch(),
            None,
        )
        .unwrap();
        assert_eq!(vote_state1, vote_state2,);
    }

    #[test]
    fn test_process_new_vote_current_state_contains_bigger_slots() {
        let mut vote_state1 = VoteState::default();
        process_slot_votes_unchecked(&mut vote_state1, &[6, 7, 8]);
        assert_eq!(
            vote_state1
                .votes
                .iter()
                .map(|vote| vote.slot)
                .collect::<Vec<Slot>>(),
            vec![6, 7, 8]
        );

        // Try to process something with lockout violations
        let bad_votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: 2,
                confirmation_count: 5,
            },
            Lockout {
                // Slot 14 could not have popped off slot 6 yet
                slot: 14,
                confirmation_count: 1,
            },
        ]
        .into_iter()
        .collect();
        let root = Some(1);

        let current_epoch = vote_state1.current_epoch();
        assert_eq!(
            process_new_vote_state(&mut vote_state1, bad_votes, root, None, current_epoch, None),
            Err(VoteError::LockoutConflict)
        );

        let good_votes: VecDeque<Lockout> = vec![
            Lockout {
                slot: 2,
                confirmation_count: 5,
            },
            Lockout {
                slot: 15,
                confirmation_count: 1,
            },
        ]
        .into_iter()
        .collect();

        let current_epoch = vote_state1.current_epoch();
        process_new_vote_state(
            &mut vote_state1,
            good_votes.clone(),
            root,
            None,
            current_epoch,
            None,
        )
        .unwrap();
        assert_eq!(vote_state1.votes, good_votes);
    }

    #[test]
    fn test_filter_old_votes() {
        // Enable feature
        let mut feature_set = FeatureSet::default();
        feature_set.activate(&filter_votes_outside_slot_hashes::id(), 0);

        let mut vote_state = VoteState::default();
        let old_vote_slot = 1;
        let vote = Vote::new(vec![old_vote_slot], Hash::default());

        // Vote with all slots that are all older than the SlotHashes history should
        // error with `VotesTooOldAllFiltered`
        let slot_hashes = vec![(3, Hash::new_unique()), (2, Hash::new_unique())];
        assert_eq!(
            process_vote(&mut vote_state, &vote, &slot_hashes, 0, Some(&feature_set),),
            Err(VoteError::VotesTooOldAllFiltered)
        );

        // Vote with only some slots older than the SlotHashes history should
        // filter out those older slots
        let vote_slot = 2;
        let vote_slot_hash = slot_hashes
            .iter()
            .find(|(slot, _hash)| *slot == vote_slot)
            .unwrap()
            .1;

        let vote = Vote::new(vec![old_vote_slot, vote_slot], vote_slot_hash);
        process_vote(&mut vote_state, &vote, &slot_hashes, 0, Some(&feature_set)).unwrap();
        assert_eq!(
            vote_state.votes.into_iter().collect::<Vec<Lockout>>(),
            vec![Lockout {
                slot: vote_slot,
                confirmation_count: 1,
            }]
        );
    }

    fn build_slot_hashes(slots: Vec<Slot>) -> Vec<(Slot, Hash)> {
        slots
            .iter()
            .rev()
            .map(|x| (*x, Hash::new_unique()))
            .collect()
    }

    fn build_vote_state(vote_slots: Vec<Slot>, slot_hashes: &[(Slot, Hash)]) -> VoteState {
        let mut vote_state = VoteState::default();

        if !vote_slots.is_empty() {
            let vote_hash = slot_hashes
                .iter()
                .find(|(slot, _hash)| slot == vote_slots.last().unwrap())
                .unwrap()
                .1;
            process_vote(
                &mut vote_state,
                &Vote::new(vote_slots, vote_hash),
                slot_hashes,
                0,
                None,
            )
            .unwrap();
        }

        vote_state
    }

    #[test]
    fn test_check_update_vote_state_empty() {
        let empty_slot_hashes = build_slot_hashes(vec![]);
        let empty_vote_state = build_vote_state(vec![], &empty_slot_hashes);

        // Test with empty vote state update, should return EmptySlots error
        let mut vote_state_update = VoteStateUpdate::from(vec![]);
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &empty_vote_state,
                &mut vote_state_update,
                &empty_slot_hashes,
                Some(&FeatureSet::all_enabled())
            ),
            Err(VoteError::EmptySlots),
        );

        // Test with non-empty vote state update, should return SlotsMismatch since nothing exists in SlotHashes
        let mut vote_state_update = VoteStateUpdate::from(vec![(0, 1)]);
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &empty_vote_state,
                &mut vote_state_update,
                &empty_slot_hashes,
                Some(&FeatureSet::all_enabled())
            ),
            Err(VoteError::SlotsMismatch),
        );
    }

    #[test]
    fn test_check_update_vote_state_too_old() {
        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
        let latest_vote = 4;
        let vote_state = build_vote_state(vec![1, 2, 3, latest_vote], &slot_hashes);

        // Test with a vote for a slot less than the latest vote in the vote_state,
        // should return error `VoteTooOld`
        let mut vote_state_update = VoteStateUpdate::from(vec![(latest_vote, 1)]);
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
                Some(&FeatureSet::all_enabled())
            ),
            Err(VoteError::VoteTooOld),
        );

        // Test with a vote state update where the latest slot `X` in the update is
        // 1) Less than the earliest slot in slot_hashes history, AND
        // 2) `X` > latest_vote
        let earliest_slot_in_history = latest_vote + 2;
        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history]);
        let mut vote_state_update = VoteStateUpdate::from(vec![(earliest_slot_in_history - 1, 1)]);
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
                Some(&FeatureSet::all_enabled()),
            ),
            Err(VoteError::VoteTooOld),
        );
    }

    fn run_test_check_update_vote_state_older_than_history_root(
        earliest_slot_in_history: Slot,
        current_vote_state_slots: Vec<Slot>,
        current_vote_state_root: Option<Slot>,
        vote_state_update_slots_and_lockouts: Vec<(Slot, u32)>,
        vote_state_update_root: Slot,
        expected_root: Option<Slot>,
        expected_vote_state: Vec<Lockout>,
    ) {
        assert!(vote_state_update_root < earliest_slot_in_history);
        assert_eq!(
            expected_root,
            current_vote_state_slots
                .iter()
                .rev()
                .find(|slot| **slot <= vote_state_update_root)
                .cloned()
        );
        let latest_slot_in_history = vote_state_update_slots_and_lockouts
            .last()
            .unwrap()
            .0
            .max(earliest_slot_in_history);
        let mut slot_hashes = build_slot_hashes(
            (current_vote_state_slots.first().copied().unwrap_or(0)..=latest_slot_in_history)
                .collect::<Vec<Slot>>(),
        );

        let mut vote_state = build_vote_state(current_vote_state_slots, &slot_hashes);
        vote_state.root_slot = current_vote_state_root;

        slot_hashes.retain(|slot| slot.0 >= earliest_slot_in_history);
        assert!(!vote_state_update_slots_and_lockouts.is_empty());
        let vote_state_update_hash = slot_hashes
            .iter()
            .find(|(slot, _hash)| *slot == vote_state_update_slots_and_lockouts.last().unwrap().0)
            .unwrap()
            .1;

        // Test with a `vote_state_update` where the root is less than `earliest_slot_in_history`.
        // Root slot in the `vote_state_update` should be updated to match the root slot in the
        // current vote state
        let mut vote_state_update = VoteStateUpdate::from(vote_state_update_slots_and_lockouts);
        vote_state_update.hash = vote_state_update_hash;
        vote_state_update.root = Some(vote_state_update_root);
        check_update_vote_state_slots_are_valid(
            &vote_state,
            &mut vote_state_update,
            &slot_hashes,
            Some(&FeatureSet::all_enabled()),
        )
        .unwrap();
        assert_eq!(vote_state_update.root, expected_root);

        // The proposed root slot should become the biggest slot in the current vote state less than
        // `earliest_slot_in_history`.
        assert!(do_process_vote_state_update(
            &mut vote_state,
            &slot_hashes,
            0,
            vote_state_update.clone(),
            Some(&FeatureSet::all_enabled()),
        )
        .is_ok());
        assert_eq!(vote_state.root_slot, expected_root);
        assert_eq!(
            vote_state.votes.into_iter().collect::<Vec<Lockout>>(),
            expected_vote_state,
        );
    }

    #[test]
    fn test_check_update_vote_state_older_than_history_root() {
        // Test when `vote_state_update_root` is in `current_vote_state_slots` but it's not the latest
        // slot
        let earliest_slot_in_history = 5;
        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
        let current_vote_state_root = None;
        let vote_state_update_slots_and_lockouts = vec![(5, 1)];
        let vote_state_update_root = 4;
        let expected_root = Some(4);
        let expected_vote_state = vec![Lockout {
            slot: 5,
            confirmation_count: 1,
        }];
        run_test_check_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );

        // Test when `vote_state_update_root` is in `current_vote_state_slots` but it's not the latest
        // slot and the `current_vote_state_root.is_some()`.
        let earliest_slot_in_history = 5;
        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
        let current_vote_state_root = Some(0);
        let vote_state_update_slots_and_lockouts = vec![(5, 1)];
        let vote_state_update_root = 4;
        let expected_root = Some(4);
        let expected_vote_state = vec![Lockout {
            slot: 5,
            confirmation_count: 1,
        }];
        run_test_check_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );

        // Test when `vote_state_update_root` is in `current_vote_state_slots` but it's not the latest
        // slot
        let earliest_slot_in_history = 5;
        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 3, 4];
        let current_vote_state_root = Some(0);
        let vote_state_update_slots_and_lockouts = vec![(4, 2), (5, 1)];
        let vote_state_update_root = 3;
        let expected_root = Some(3);
        let expected_vote_state = vec![
            Lockout {
                slot: 4,
                confirmation_count: 2,
            },
            Lockout {
                slot: 5,
                confirmation_count: 1,
            },
        ];
        run_test_check_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );

        // Test when `vote_state_update_root` is not in `current_vote_state_slots`
        let earliest_slot_in_history = 5;
        let current_vote_state_slots: Vec<Slot> = vec![1, 2, 4];
        let current_vote_state_root = Some(0);
        let vote_state_update_slots_and_lockouts = vec![(4, 2), (5, 1)];
        let vote_state_update_root = 3;
        let expected_root = Some(2);
        let expected_vote_state = vec![
            Lockout {
                slot: 4,
                confirmation_count: 2,
            },
            Lockout {
                slot: 5,
                confirmation_count: 1,
            },
        ];
        run_test_check_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );

        // Test when the `vote_state_update_root` is smaller than all the slots in
        // `current_vote_state_slots`, no roots should be set.
        let earliest_slot_in_history = 4;
        let current_vote_state_slots: Vec<Slot> = vec![3, 4];
        let current_vote_state_root = None;
        let vote_state_update_slots_and_lockouts = vec![(3, 3), (4, 2), (5, 1)];
        let vote_state_update_root = 2;
        let expected_root = None;
        let expected_vote_state = vec![
            Lockout {
                slot: 3,
                confirmation_count: 3,
            },
            Lockout {
                slot: 4,
                confirmation_count: 2,
            },
            Lockout {
                slot: 5,
                confirmation_count: 1,
            },
        ];
        run_test_check_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );

        // Test when `current_vote_state_slots` is empty, no roots should be set
        let earliest_slot_in_history = 4;
        let current_vote_state_slots: Vec<Slot> = vec![];
        let current_vote_state_root = None;
        let vote_state_update_slots_and_lockouts = vec![(5, 1)];
        let vote_state_update_root = 2;
        let expected_root = None;
        let expected_vote_state = vec![Lockout {
            slot: 5,
            confirmation_count: 1,
        }];
        run_test_check_update_vote_state_older_than_history_root(
            earliest_slot_in_history,
            current_vote_state_slots,
            current_vote_state_root,
            vote_state_update_slots_and_lockouts,
            vote_state_update_root,
            expected_root,
            expected_vote_state,
        );
    }

    #[test]
    fn test_check_update_vote_state_slots_not_ordered() {
        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
        let vote_state = build_vote_state(vec![1], &slot_hashes);

        // Test with a `vote_state_update` where the slots are out of order
        let vote_slot = 3;
        let vote_slot_hash = slot_hashes
            .iter()
            .find(|(slot, _hash)| *slot == vote_slot)
            .unwrap()
            .1;
        let mut vote_state_update = VoteStateUpdate::from(vec![(2, 2), (1, 3), (vote_slot, 1)]);
        vote_state_update.hash = vote_slot_hash;
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
                Some(&FeatureSet::all_enabled())
            ),
            Err(VoteError::SlotsNotOrdered),
        );

        // Test with a `vote_state_update` where there are multiples of the same slot
        let mut vote_state_update = VoteStateUpdate::from(vec![(2, 2), (2, 2), (vote_slot, 1)]);
        vote_state_update.hash = vote_slot_hash;
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
                Some(&FeatureSet::all_enabled()),
            ),
            Err(VoteError::SlotsNotOrdered),
        );
    }

    #[test]
    fn test_check_update_vote_state_older_than_history_slots_filtered() {
        let slot_hashes = build_slot_hashes(vec![1, 2, 3, 4]);
        let mut vote_state = build_vote_state(vec![1, 2, 3, 4], &slot_hashes);

        // Test with a `vote_state_update` where there:
        // 1) Exists a slot less than `earliest_slot_in_history`
        // 2) This slot does not exist in the vote state already
        // This slot should be filtered out
        let earliest_slot_in_history = 11;
        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
        let vote_slot = 12;
        let vote_slot_hash = slot_hashes
            .iter()
            .find(|(slot, _hash)| *slot == vote_slot)
            .unwrap()
            .1;
        let missing_older_than_history_slot = earliest_slot_in_history - 1;
        let mut vote_state_update = VoteStateUpdate::from(vec![
            (1, 4),
            (missing_older_than_history_slot, 2),
            (vote_slot, 3),
        ]);
        vote_state_update.hash = vote_slot_hash;
        check_update_vote_state_slots_are_valid(
            &vote_state,
            &mut vote_state_update,
            &slot_hashes,
            Some(&FeatureSet::all_enabled()),
        )
        .unwrap();

        // Check the earlier slot was filtered out
        assert_eq!(
            vote_state_update
                .clone()
                .lockouts
                .into_iter()
                .collect::<Vec<Lockout>>(),
            vec![
                Lockout {
                    slot: 1,
                    confirmation_count: 4,
                },
                Lockout {
                    slot: vote_slot,
                    confirmation_count: 3,
                }
            ]
        );
        assert!(do_process_vote_state_update(
            &mut vote_state,
            &slot_hashes,
            0,
            vote_state_update,
            Some(&FeatureSet::all_enabled()),
        )
        .is_ok());
    }

    #[test]
    fn test_check_update_vote_state_older_than_history_slots_not_filtered() {
        let slot_hashes = build_slot_hashes(vec![4]);
        let mut vote_state = build_vote_state(vec![4], &slot_hashes);

        // Test with a `vote_state_update` where there:
        // 1) Exists a slot less than `earliest_slot_in_history`
        // 2) This slot exists in the vote state already
        // This slot should *NOT* be filtered out
        let earliest_slot_in_history = 11;
        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
        let vote_slot = 12;
        let vote_slot_hash = slot_hashes
            .iter()
            .find(|(slot, _hash)| *slot == vote_slot)
            .unwrap()
            .1;
        let existing_older_than_history_slot = 4;
        let mut vote_state_update =
            VoteStateUpdate::from(vec![(existing_older_than_history_slot, 3), (vote_slot, 2)]);
        vote_state_update.hash = vote_slot_hash;
        check_update_vote_state_slots_are_valid(
            &vote_state,
            &mut vote_state_update,
            &slot_hashes,
            Some(&FeatureSet::all_enabled()),
        )
        .unwrap();
        // Check the earlier slot was *NOT* filtered out
        assert_eq!(vote_state_update.lockouts.len(), 2);
        assert_eq!(
            vote_state_update
                .clone()
                .lockouts
                .into_iter()
                .collect::<Vec<Lockout>>(),
            vec![
                Lockout {
                    slot: existing_older_than_history_slot,
                    confirmation_count: 3,
                },
                Lockout {
                    slot: vote_slot,
                    confirmation_count: 2,
                }
            ]
        );
        assert!(do_process_vote_state_update(
            &mut vote_state,
            &slot_hashes,
            0,
            vote_state_update,
            Some(&FeatureSet::all_enabled()),
        )
        .is_ok());
    }

    #[test]
    fn test_check_update_vote_state_older_than_history_slots_filtered_and_not_filtered() {
        let slot_hashes = build_slot_hashes(vec![6]);
        let mut vote_state = build_vote_state(vec![6], &slot_hashes);

        // Test with a `vote_state_update` where there exists both a slot:
        // 1) Less than `earliest_slot_in_history`
        // 2) This slot exists in the vote state already
        // which should not be filtered
        //
        // AND a slot that
        //
        // 1) Less than `earliest_slot_in_history`
        // 2) This slot does not exist in the vote state already
        // which should be filtered
        let earliest_slot_in_history = 11;
        let slot_hashes = build_slot_hashes(vec![earliest_slot_in_history, 12, 13, 14]);
        let vote_slot = 14;
        let vote_slot_hash = slot_hashes
            .iter()
            .find(|(slot, _hash)| *slot == vote_slot)
            .unwrap()
            .1;

        let missing_older_than_history_slot = 4;
        let existing_older_than_history_slot = 6;

        let mut vote_state_update = VoteStateUpdate::from(vec![
            (missing_older_than_history_slot, 4),
            (existing_older_than_history_slot, 3),
            (12, 2),
            (vote_slot, 1),
        ]);
        vote_state_update.hash = vote_slot_hash;
        check_update_vote_state_slots_are_valid(
            &vote_state,
            &mut vote_state_update,
            &slot_hashes,
            Some(&FeatureSet::all_enabled()),
        )
        .unwrap();
        assert_eq!(vote_state_update.lockouts.len(), 3);
        assert_eq!(
            vote_state_update
                .clone()
                .lockouts
                .into_iter()
                .collect::<Vec<Lockout>>(),
            vec![
                Lockout {
                    slot: existing_older_than_history_slot,
                    confirmation_count: 3,
                },
                Lockout {
                    slot: 12,
                    confirmation_count: 2,
                },
                Lockout {
                    slot: vote_slot,
                    confirmation_count: 1,
                }
            ]
        );
        assert!(do_process_vote_state_update(
            &mut vote_state,
            &slot_hashes,
            0,
            vote_state_update,
            Some(&FeatureSet::all_enabled()),
        )
        .is_ok());
    }

    #[test]
    fn test_check_update_vote_state_slot_not_on_fork() {
        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
        let vote_state = build_vote_state(vec![2, 4, 6], &slot_hashes);

        // Test with a `vote_state_update` where there:
        // 1) Exists a slot not in the slot hashes history
        // 2) The slot is greater than the earliest slot in the history
        // Thus this slot is not part of the fork and the update should be rejected
        // with error `SlotsMismatch`
        let missing_vote_slot = 3;

        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
        // errors
        let vote_slot = vote_state.votes.back().unwrap().slot + 2;
        let vote_slot_hash = slot_hashes
            .iter()
            .find(|(slot, _hash)| *slot == vote_slot)
            .unwrap()
            .1;
        let mut vote_state_update =
            VoteStateUpdate::from(vec![(missing_vote_slot, 2), (vote_slot, 3)]);
        vote_state_update.hash = vote_slot_hash;
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
                Some(&FeatureSet::all_enabled())
            ),
            Err(VoteError::SlotsMismatch),
        );

        // Test where some earlier vote slots exist in the history, but others don't
        let missing_vote_slot = 7;
        let mut vote_state_update = VoteStateUpdate::from(vec![
            (2, 5),
            (4, 4),
            (6, 3),
            (missing_vote_slot, 2),
            (vote_slot, 1),
        ]);
        vote_state_update.hash = vote_slot_hash;
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
                Some(&FeatureSet::all_enabled())
            ),
            Err(VoteError::SlotsMismatch),
        );
    }

    #[test]
    fn test_check_update_vote_state_root_on_different_fork() {
        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
        let vote_state = build_vote_state(vec![6], &slot_hashes);

        // Test with a `vote_state_update` where:
        // 1) The root is not present in slot hashes history
        // 2) The slot is greater than the earliest slot in the history
        // Thus this slot is not part of the fork and the update should be rejected
        // with error `RootOnDifferentFork`
        let new_root = 3;

        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
        // errors, but also this slot must be present in SlotHashes
        let vote_slot = 8;
        assert_eq!(vote_slot, slot_hashes.first().unwrap().0);
        let vote_slot_hash = slot_hashes
            .iter()
            .find(|(slot, _hash)| *slot == vote_slot)
            .unwrap()
            .1;
        let mut vote_state_update = VoteStateUpdate::from(vec![(vote_slot, 1)]);
        vote_state_update.hash = vote_slot_hash;
        vote_state_update.root = Some(new_root);
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
                Some(&FeatureSet::all_enabled())
            ),
            Err(VoteError::RootOnDifferentFork),
        );
    }

    #[test]
    fn test_check_update_vote_state_slot_newer_than_slot_history() {
        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
        let vote_state = build_vote_state(vec![2, 4, 6], &slot_hashes);

        // Test with a `vote_state_update` where there:
        // 1) The last slot in the update is a slot not in the slot hashes history
        // 2) The slot is greater than the newest slot in the slot history
        // Thus this slot is not part of the fork and the update should be rejected
        // with error `SlotsMismatch`
        let missing_vote_slot = slot_hashes.first().unwrap().0 + 1;
        let vote_slot_hash = Hash::new_unique();
        let mut vote_state_update = VoteStateUpdate::from(vec![(8, 2), (missing_vote_slot, 3)]);
        vote_state_update.hash = vote_slot_hash;
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
                Some(&FeatureSet::all_enabled())
            ),
            Err(VoteError::SlotsMismatch),
        );
    }

    #[test]
    fn test_check_update_vote_state_slot_all_slot_hashes_in_update_ok() {
        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
        let mut vote_state = build_vote_state(vec![2, 4, 6], &slot_hashes);

        // Test with a `vote_state_update` where every slot in the history is
        // in the update

        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
        // errors
        let vote_slot = vote_state.votes.back().unwrap().slot + 2;
        let vote_slot_hash = slot_hashes
            .iter()
            .find(|(slot, _hash)| *slot == vote_slot)
            .unwrap()
            .1;
        let mut vote_state_update =
            VoteStateUpdate::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
        vote_state_update.hash = vote_slot_hash;
        check_update_vote_state_slots_are_valid(
            &vote_state,
            &mut vote_state_update,
            &slot_hashes,
            Some(&FeatureSet::all_enabled()),
        )
        .unwrap();

        // Nothing in the update should have been filtered out
        assert_eq!(
            vote_state_update
                .clone()
                .lockouts
                .into_iter()
                .collect::<Vec<Lockout>>(),
            vec![
                Lockout {
                    slot: 2,
                    confirmation_count: 4,
                },
                Lockout {
                    slot: 4,
                    confirmation_count: 3,
                },
                Lockout {
                    slot: 6,
                    confirmation_count: 2,
                },
                Lockout {
                    slot: vote_slot,
                    confirmation_count: 1,
                }
            ]
        );

        assert!(do_process_vote_state_update(
            &mut vote_state,
            &slot_hashes,
            0,
            vote_state_update,
            Some(&FeatureSet::all_enabled()),
        )
        .is_ok());
    }

    #[test]
    fn test_check_update_vote_state_slot_some_slot_hashes_in_update_ok() {
        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8, 10]);
        let mut vote_state = build_vote_state(vec![6], &slot_hashes);

        // Test with a `vote_state_update` where only some slots in the history are
        // in the update, and others slots in the history are missing.

        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
        // errors
        let vote_slot = vote_state.votes.back().unwrap().slot + 2;
        let vote_slot_hash = slot_hashes
            .iter()
            .find(|(slot, _hash)| *slot == vote_slot)
            .unwrap()
            .1;
        let mut vote_state_update = VoteStateUpdate::from(vec![(4, 2), (vote_slot, 1)]);
        vote_state_update.hash = vote_slot_hash;
        check_update_vote_state_slots_are_valid(
            &vote_state,
            &mut vote_state_update,
            &slot_hashes,
            Some(&FeatureSet::all_enabled()),
        )
        .unwrap();

        // Nothing in the update should have been filtered out
        assert_eq!(
            vote_state_update
                .clone()
                .lockouts
                .into_iter()
                .collect::<Vec<Lockout>>(),
            vec![
                Lockout {
                    slot: 4,
                    confirmation_count: 2,
                },
                Lockout {
                    slot: vote_slot,
                    confirmation_count: 1,
                }
            ]
        );

        // Because 6 from the original VoteState
        // should not have been popped off in the proposed state,
        // we should get a lockout conflict
        assert_eq!(
            do_process_vote_state_update(
                &mut vote_state,
                &slot_hashes,
                0,
                vote_state_update,
                Some(&FeatureSet::all_enabled())
            ),
            Err(VoteError::LockoutConflict)
        );
    }

    #[test]
    fn test_check_update_vote_state_slot_hash_mismatch() {
        let slot_hashes = build_slot_hashes(vec![2, 4, 6, 8]);
        let vote_state = build_vote_state(vec![2, 4, 6], &slot_hashes);

        // Test with a `vote_state_update` where the hash is mismatched

        // Have to vote for a slot greater than the last vote in the vote state to avoid VoteTooOld
        // errors
        let vote_slot = vote_state.votes.back().unwrap().slot + 2;
        let vote_slot_hash = Hash::new_unique();
        let mut vote_state_update =
            VoteStateUpdate::from(vec![(2, 4), (4, 3), (6, 2), (vote_slot, 1)]);
        vote_state_update.hash = vote_slot_hash;
        assert_eq!(
            check_update_vote_state_slots_are_valid(
                &vote_state,
                &mut vote_state_update,
                &slot_hashes,
                Some(&FeatureSet::all_enabled())
            ),
            Err(VoteError::SlotHashMismatch),
        );
    }
}
